package com.zcy.dynamic;
 
import com.zcy.annotation.ServiceAnnotationScanner;
import org.springframework.beans.factory.annotation.AnnotatedGenericBeanDefinition;
import org.springframework.beans.factory.config.BeanDefinitionHolder;
import org.springframework.beans.factory.support.BeanDefinitionReaderUtils;
import org.springframework.beans.factory.support.BeanNameGenerator;
import org.springframework.context.annotation.*;
import org.springframework.stereotype.Component;

import java.io.File;
import java.net.URL;
import java.net.URLClassLoader;
import java.util.ArrayList;
import java.util.List;

import javax.tools.Diagnostic;
import javax.tools.DiagnosticCollector;
import javax.tools.JavaCompiler;
import javax.tools.JavaFileObject;
import javax.tools.ToolProvider;


/**
 * @author :join
 * @Comments 原理：
 * 在Java SE6及以后版本中提供了一个很好的工具类——StandardJavaFileManager。
 * 这个类可以很好地控制输入、输出，并且可以通过DiagnosticListener得到编译信息，
 * DiagnosticCollector类就是listener的实现，使用StandardJavaFileManager需要两步。
 * 首先建立一个DiagnosticCollector实例以及通过JavaCompiler的getStandardFileManager()方法得到一个StandardFileManager对象。
 * 最后通过CompilationTask中的call方法编译源程序。
 * @Version    : 1.0.0
 */
@Component
public class DynamicCompileEngine {

    private ScopeMetadataResolver scopeMetadataResolver = new AnnotationScopeMetadataResolver();
    private BeanNameGenerator beanNameGenerator = new AnnotationBeanNameGenerator();

    private URLClassLoader parentClassLoader;
    private String classpath;
    //单例
    private static DynamicCompileEngine ourInstance = new DynamicCompileEngine();
 
    public static DynamicCompileEngine getInstance() {
        return ourInstance;
    }

    private DynamicCompileEngine() {
        //获取类加载器
        this.parentClassLoader = (URLClassLoader) this.getClass().getClassLoader();
        //创建classpath
        this.buildClassPath();
    }
    
   
    /**
     * @MethodName	: 创建classpath
     * @Description	: TODO
     */
    private void buildClassPath() {
        this.classpath = null;
        StringBuilder sb = new StringBuilder();
        for (URL url : this.parentClassLoader.getURLs()) {
            String p = url.getFile();
            sb.append(p).append(File.pathSeparator);
        }
        this.classpath = sb.toString();
    }
    
    /**
     * @MethodName	: 编译java代码,并将编译后的class加载到类加载其中，返回Class对象,同时将class文件写入到claspath中
     * @Description	: TODO
     * @param fullClassName   类名
     * @param javaCode  类代码
     * @return Class
     * @throws IllegalAccessException
     * @throws InstantiationException
     */
    public Class compileAndLoad(String fullClassName, String javaCode) throws Exception {
        long start = System.currentTimeMillis();
        Class clazz = null;
        //获取系统编译器
        JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();
        DiagnosticCollector<JavaFileObject> diagnostics = new DiagnosticCollector<JavaFileObject>();
        
         // 建立用于保存被编译文件名的对象
         // 每个文件被保存在一个从JavaFileObject继承的类中
        ClassFileManager fileManager = new ClassFileManager(compiler.getStandardFileManager(diagnostics, null, null));
 
        List<JavaFileObject> jfiles = new ArrayList<JavaFileObject>();
        jfiles.add(new CharSequenceJavaFileObject(fullClassName, javaCode));
 
        //设置编译参数
        List<String> options = new ArrayList<String>();
        options.add("-encoding");
        options.add("UTF-8");
        options.add("-classpath");
        options.add(this.classpath);

        //获取编译任务并启动编译
        JavaCompiler.CompilationTask task = compiler.getTask(null, fileManager, diagnostics, options, null, jfiles);
        boolean success = task.call();

        //编译成功，加载类
        if (success) {
            JavaClassObject jco = fileManager.getJavaClassObject();
            URL url = this.getClass().getClassLoader().getResource("");
            //jco.writeToFile(url.getPath() + "com/zcy/service/impl/SysModfService.Class");
            jco.writeToFile(url.getPath() + fullClassName.replaceAll("\\.",File.separator) + ".Class");

            DynamicClassLoader dynamicClassLoader = new DynamicClassLoader(this.parentClassLoader);
            clazz = dynamicClassLoader.loadClass(fullClassName,jco);

        } else {
            //获取编译错误信息
            String error = "";
            for (Diagnostic diagnostic : diagnostics.getDiagnostics()) {
                error = error + compileInfo(diagnostic);
            }
            throw new Exception(error);
        }
        long end = System.currentTimeMillis();
        System.out.println("compile And Load Class[" + fullClassName + "] use:"+(end-start)+"ms");
        return clazz;
    }

    /**
     * @MethodName	: 编译java代码,并将编译后的class加载到类加载其中，返回实例对象
     * @Description	: TODO
     * @param fullClassName   类名
     * @param javaCode  类代码
     * @return Object
     * @throws IllegalAccessException
     * @throws InstantiationException
     */
    public Object compileAndInstance(String fullClassName, String javaCode) throws Exception {
        long start = System.currentTimeMillis(); //记录开始编译时间
        Object instance = null;
        Class clazz = this.compileAndLoad(fullClassName,javaCode);
        instance = clazz.newInstance();
        long end = System.currentTimeMillis();
        System.out.println("compile And Instance Class[" + fullClassName + "] use:"+(end-start)+"ms");
        return instance;
    }


    /**
     * @MethodName	: 编译java代码,并将编译后的class加载到类加载其中，然后注册成为springBean
     * @Description	: TODO
     * @param fullClassName   类名
     * @param javaCode  类代码
     * @param beanId  spring bean 的id
     * @return String 返回spring bean id
     * @throws IllegalAccessException
     * @throws InstantiationException
     */
    public String compileToSpringBean(String fullClassName, String javaCode,String beanId) throws Exception {
        Class clazz = this.compileAndLoad(fullClassName,javaCode);
        AnnotatedGenericBeanDefinition abd = new AnnotatedGenericBeanDefinition(clazz);

        ScopeMetadata scopeMetadata = this.scopeMetadataResolver.resolveScopeMetadata(abd);
        abd.setScope(scopeMetadata.getScopeName());

        String beanName = (beanId != null ? beanId : this.beanNameGenerator.generateBeanName(abd, DynamicBeanDefinitionRegistryPostProcessor.registry));

        AnnotationConfigUtils.processCommonDefinitionAnnotations(abd);
        BeanDefinitionHolder definitionHolder = new BeanDefinitionHolder(abd, beanName);
        BeanDefinitionReaderUtils.registerBeanDefinition(definitionHolder, DynamicBeanDefinitionRegistryPostProcessor.registry);

        return beanName;
    }

    /**
     * @MethodName  : 编译java代码,并将编译后的class加载到类加载其中，然后注册成为springBean,同时注册成为Hessian服务
     * @param fullClassName   类名
     * @param javaCode  类代码
     * @param beanId  spring bean 的id
     * @return String 返回spring bean id
     * @throws Exception
     */
    public String compileToSpringBeanAndRegistService(String fullClassName, String javaCode,String beanId) throws Exception {
        Class clazz = this.compileAndLoad(fullClassName,javaCode);
        AnnotatedGenericBeanDefinition abd = new AnnotatedGenericBeanDefinition(clazz);

        ScopeMetadata scopeMetadata = this.scopeMetadataResolver.resolveScopeMetadata(abd);
        abd.setScope(scopeMetadata.getScopeName());

        String beanName = (beanId != null ? beanId : this.beanNameGenerator.generateBeanName(abd, DynamicBeanDefinitionRegistryPostProcessor.registry));

        AnnotationConfigUtils.processCommonDefinitionAnnotations(abd);
        BeanDefinitionHolder definitionHolder = new BeanDefinitionHolder(abd, beanName);
        BeanDefinitionReaderUtils.registerBeanDefinition(definitionHolder, DynamicBeanDefinitionRegistryPostProcessor.registry);

        try {
            ServiceAnnotationScanner.registService(clazz,beanName);
        } catch (NoSuchMethodException e) {
            e.printStackTrace();
        }

        return beanName;
    }
 
    /**
     * @MethodName	: compilePrint
     * @Description	: 组装编译编译信息
     * @param diagnostic
     * @return
     */
    private String compileInfo(Diagnostic diagnostic) {
        StringBuffer res = new StringBuffer();
        res.append("Code:[" + diagnostic.getCode() + "]\n");
        res.append("Kind:[" + diagnostic.getKind() + "]\n");
        res.append("Position:[" + diagnostic.getPosition() + "]\n");
        res.append("Start Position:[" + diagnostic.getStartPosition() + "]\n");
        res.append("End Position:[" + diagnostic.getEndPosition() + "]\n");
        res.append("Source:[" + diagnostic.getSource() + "]\n");
        res.append("Message:[" + diagnostic.getMessage(null) + "]\n");
        res.append("LineNumber:[" + diagnostic.getLineNumber() + "]\n");
        res.append("ColumnNumber:[" + diagnostic.getColumnNumber() + "]\n");
        return res.toString();
    }
}